package solvers;

import org.gnu.glpk.*;

public class RivalSolver {
	private static final Object global_mutex = new Object();
	private static final int task_number = 0;
	
	final int m;
	final int n;
	final int columns;
	final int rows;
	final int[][] I_j;

	final SWIGTYPE_p_int ind;
	final SWIGTYPE_p_double val;

	final glp_prob lp;

	boolean closed = false;

	public int getM() {
		return m;
	}

	public int getN() {
		return n;
	}

	public RivalSolver(int m, int n) {
		this.m = m;
		this.n = n;
		columns = n + 2 * m;
		rows = n + m + 1;

		I_j = new int[n][m];

    // Create problem
    lp = GLPK.glp_create_prob();
    GLPK.glp_set_prob_name(lp, "Rival_problem" + (task_number+1));

    // Next, build matrix same this:
    //   x      y     1-z
    // 0...0  1...1  0...0  = r (c5)
    //
    //   0  (-Ij(x))   E    = ? (c6)
    //
    //   E      E      0    = ? (c7)
    //
    // And find solution.

		// Define columns
		GLPK.glp_add_cols(lp, columns);
		for (int i = 0; i < columns; i++) {
			if (i < m)
				GLPK.glp_set_col_name(lp, i + 1, "x" + (i + 1));
			else if (i >= m && i < 2 * m)
				GLPK.glp_set_col_name(lp, i + 1, "y" + (i - m + 1));
			else
				GLPK.glp_set_col_name(lp, i + 1, "1-z" + (i - 2 * m + 1));
			GLPK.glp_set_col_kind(lp, i + 1, GLPKConstants.GLP_BV);
		}

		// Create constraints
		GLPK.glp_add_rows(lp, rows);
		ind = GLPK.new_intArray(columns + 1);
		val = GLPK.new_doubleArray(columns + 1);
	}

	public void close() {
		if (closed)
			throw new IllegalStateException("solver is already closed");
		closed = true;
		// Free memory
		GLPK.glp_delete_prob(lp);
	}
    
	public RivalResult solve(int[] x, int[] w, double[][] g, int r) {
		if (closed)
			throw new IllegalStateException("solver is closed");
		// Check input data
		if (x == null || w == null || w == null || g == null)
			throw new IllegalArgumentException(
					"x == null || w == null || w == null || g == null");

		if (g.length != w.length)
			throw new IllegalArgumentException("g.length != w.length");

		for (int i = 0; i < g.length; i++) {
			if (g[i].length != x.length)
				throw new IllegalArgumentException("g[i].length != x.length");
		}

		for (int j = 0; j < n; j++) {
			for (int i = 0; i < m; i++) {
				double min = Double.MAX_VALUE;
				for (int l = 0; l < m; l++) {
					if (x[l] == 1 && min > g[l][j]) {
						min = g[l][j];
					}
				}
				if (g[i][j] < min) {
					I_j[j][i] = 1;
				} else {
					I_j[j][i] = 0;
				}
			}
		}
		
		RivalResult result = null;
		int ret = 0;
		// C5: sum(y_i) = r
		GLPK.glp_set_row_name(lp, 1, "c5");
		GLPK.glp_set_row_bnds(lp, 1, GLPKConstants.GLP_FX, r, r);

		for (int i = 0; i < columns; i++) {
			if (i < m) {
				GLPK.intArray_setitem(ind, i + 1, i + 1);
				GLPK.doubleArray_setitem(val, i + 1, 0);
			} else if (i >= m && i < 2 * m) {
				GLPK.intArray_setitem(ind, i + 1, i + 1);
				GLPK.doubleArray_setitem(val, i + 1, 1);
			} else {
				GLPK.intArray_setitem(ind, i + 1, i + 1);
				GLPK.doubleArray_setitem(val, i + 1, 0);
			}
		}
		GLPK.glp_set_mat_row(lp, 1, columns, ind, val);

		// C6: 1-z_j <= sum(y_i) where i from I_j. For j from J
		for (int j = 0; j < n; j++) {
			GLPK.glp_set_row_name(lp, j + 2, "c6_" + j);
			GLPK.glp_set_row_bnds(lp, j + 2, GLPKConstants.GLP_UP, 0, 0);
			// ind = GLPK.new_intArray(columns + 1);
			// val = GLPK.new_doubleArray(columns + 1);
			for (int i = 0; i < columns; i++) {
				if (i < m) {
					GLPK.intArray_setitem(ind, i + 1, i + 1);
					GLPK.doubleArray_setitem(val, i + 1, 0);
				} else if (i >= m && i < 2 * m) {
					GLPK.intArray_setitem(ind, i + 1, i + 1);
					GLPK.doubleArray_setitem(val, i + 1, -I_j[j][i - m]);
				} else {
					GLPK.intArray_setitem(ind, i + 1, i + 1);
					if ((i - 2 * m) == j)
						GLPK.doubleArray_setitem(val, i + 1, 1);
					else
						GLPK.doubleArray_setitem(val, i + 1, 0);
				}
			}
			GLPK.glp_set_mat_row(lp, j + 2, columns, ind, val);
		}

		// C7: x_i + y_i <= 1 for i from I
		for (int j = 0; j < m; j++) {
			GLPK.glp_set_row_name(lp, j + n + 2, "c7_" + j);
			GLPK.glp_set_row_bnds(lp, j + n + 2, GLPKConstants.GLP_UP, 0, 1);
			// ind = GLPK.new_intArray(columns + 1);
			// val = GLPK.new_doubleArray(columns + 1);
			for (int i = 0; i < columns; i++) {
				if (i < m) {
					GLPK.intArray_setitem(ind, i + 1, i + 1);
					if (i == j)
						GLPK.doubleArray_setitem(val, i + 1, 1);
					else
						GLPK.doubleArray_setitem(val, i + 1, 0);
				} else if (i >= m && i < 2 * m) {
					GLPK.intArray_setitem(ind, i + 1, i + 1);
					if ((i - m) == j)
						GLPK.doubleArray_setitem(val, i + 1, 1);
					else
						GLPK.doubleArray_setitem(val, i + 1, 0);
				} else {
					GLPK.intArray_setitem(ind, i + 1, i + 1);
					GLPK.doubleArray_setitem(val, i + 1, 0);
				}
			}
			GLPK.glp_set_mat_row(lp, j + n + 2, columns, ind, val);
		}

		// Result
		GLPK.glp_set_obj_name(lp, "Task Name");
		GLPK.glp_set_obj_dir(lp, GLPKConstants.GLP_MAX);
		GLPK.glp_set_obj_coef(lp, 0, 0);
		for (int i = 0; i < columns; ++i) {
			if (i < m) {
				GLPK.glp_set_obj_coef(lp, i + 1, 0);
			} else if (i >= m && i < 2 * m) {
				GLPK.glp_set_obj_coef(lp, i + 1, 0);
			} else {
				GLPK.glp_set_obj_coef(lp, i + 1, w[i - 2 * m]);
			}
		}			
		
		// Solve model
		glp_iocp iocp = new glp_iocp();
		GLPK.glp_init_iocp(iocp);
		iocp.setPresolve(GLPKConstants.GLP_ON);
		synchronized (global_mutex) {
			ret = GLPK.glp_intopt(lp, iocp);
		}

		// Retrieve solution
		if (ret == 0) {
			result = new RivalResult();
			result.income = GLPK.glp_mip_obj_val(lp);
			result.y = new int[m];
			// int n_cols = GLPK.glp_get_num_cols(lp);
			for (int i = m + 1; i < 2 * m + 1; i++) {
				double v = GLPK.glp_mip_col_val(lp, i);
				result.y[i - m - 1] = (int) (v + 0.5);
			}

			result.z = new int[m];
			for (int i = 2 * m + 1; i < 3 * m + 1; i++) {
				double v = GLPK.glp_mip_col_val(lp, i);
				result.z[i - 2 * m - 1] = (int) (1 - v + 0.5);
			}
		}

		return result;
	}
}